/*
 *  Copyright 2010 INFN - APE group
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

#include <functional>
#include <algorithm>

//#include <cuos/cuos.h>
#include <cuos/detail/pinned_mem_pool.h>

//----------------------------------------------------------------------

cuos::host::pinned_mem_pool::desc cuos::host::pinned_mem_pool::alloc_desc(size_t req_size)
{
    desc d;
    assert(req_size > 0);
    assert(cuqu::isPow2(min_size));
    // round to nearest multiple of min_size
    size_t size = (req_size + min_size - 1) & ~(min_size-1);
    d.size = size;
    cuosSafeCall(cudaHostAlloc(&d.h_ptr, size, cudaHostAllocMapped|cudaHostAllocPortable));
    cuosSafeCall(cudaHostGetDevicePointer(&d.d_ptr, d.h_ptr, 0));
    cuosTrace("allocated pinned buf h_ptr=%p d_ptr=%p req_size=%d size=%d\n", d.h_ptr, d.d_ptr, req_size, d.size);
    return d;
}

//----------------------------------------------------------------------

void cuos::host::pinned_mem_pool::populate()
{
    int n_bufs = 0;
    size_t max_size = m_max_size;
    assert(!m_populated);
    cuosTrace("populating memory pool, max_size=%d\n", max_size);
    assert(max_size > 0);
    for(int size = min_size; size <= max_size; size *= size_factor) {
        for(int bs = 0; bs < n_bufs_per_size; ++bs) {
            const desc &d = alloc_desc(size);
            m_pool.insert(d);
        }
        n_bufs += n_bufs_per_size;
    }
    m_populated = true;
    cuosTrace("init phase ended\n");
}

//----------------------------------------------------------------------

void cuos::host::pinned_mem_pool::free()
{
    int n_still_around = m_n_alloc - m_n_released;
    cuosTrace("freeing buffers: n_alloc=%d n_release=%d n_hits=%d\n", m_n_alloc, m_n_released, m_n_hits);
    if(n_still_around != 0)
        cuosError("%d buffers are still allocated!!!\n", n_still_around);
    if(n_still_around != m_alloced.size())
        cuosError("there are %d buffers around, but different from %d unreturned buffers\n", n_still_around,  m_alloced.size());
    //for(pool_t::iterator i=m_pool.begin(); i!=m_pool.end(); ++i)
    std::for_each(m_pool.begin(), m_pool.end(), desc_free());
}

//----------------------------------------------------------------------

void *cuos::host::pinned_mem_pool::find(size_t size)
{
    assert(size > 0);
    // find
    pool_t::iterator i = std::find_if(m_pool.begin(), m_pool.end(), desc_find_by_size(size));
    if(i == m_pool.end()) {
        cuosError("can't find size=%d buffer\n", size);
        return 0;
    }
    desc d = *i;
    assert(d.h_ptr != 0);
    assert(d.size >= size);
    m_pool.erase(i);
    // add to m_alloced
    m_alloced.insert(d);
    ++m_n_hits;
    return d.h_ptr;
}

//----------------------------------------------------------------------

void *cuos::host::pinned_mem_pool::alloc(size_t size)
{
    void *ret;

    if(!m_populated)
        populate();

    ret = find(size);
    if(!ret) {
        const desc &d = alloc_desc(size);
        m_alloced.insert(d);
        ret = d.h_ptr;
    }
    cuosTrace("alloced buffer h_ptr=%p size=%d\n", ret, size);
    ++ m_n_alloc;
    return ret;
}

//----------------------------------------------------------------------

void cuos::host::pinned_mem_pool::release(void *h_ptr)
{
    assert(h_ptr);
    // find ptr in m_alloced
    pool_t::iterator i = std::find_if(m_alloced.begin(), m_alloced.end(), desc_find_by_h_addr(h_ptr));
    if(i == m_alloced.end()) {
        cuosError("can't find buf %p in alloced list\n", h_ptr);
        return;
    }
    desc d = *i;
    m_alloced.erase(i);
    assert(d.h_ptr == h_ptr);
    m_pool.insert(d);
    ++ m_n_released;
}

//----------------------------------------------------------------------
/*
 * Local variables:
 *  mode: c++
 *  c-indent-level: 4
 *  c-basic-offset: 4
 *  tab-width: 4
 *  indent-tabs-mode: nil
 * End:
 */
